package top.conangao.common.security.handler;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.*;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import top.conangao.common.constant.CacheConstants;
import top.conangao.common.holder.UserContextHolder;
import top.conangao.common.security.domain.SecurityUser;

import java.net.URI;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ConanGao
 * @description 自定义OpaqueTokenIntrospector
 * @since 1.0
 **/
@Slf4j
public class CustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
    private static final String AUTHORITY_PREFIX = "SCOPE_";
    private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
    };
    private final RestOperations restOperations;
    private Converter<String, RequestEntity<?>> requestEntityConverter;
    @Autowired
    private RedissonClient redissonClient;

    public CustomOpaqueTokenIntrospector(String introspectionUri, String clientId, String clientSecret) {
        Assert.notNull(introspectionUri, "introspectionUri cannot be null");
        Assert.notNull(clientId, "clientId cannot be null");
        Assert.notNull(clientSecret, "clientSecret cannot be null");
        this.requestEntityConverter = this.defaultRequestEntityConverter(URI.create(introspectionUri));
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(clientId, clientSecret));
        this.restOperations = restTemplate;
    }

    public CustomOpaqueTokenIntrospector(String introspectionUri, RestOperations restOperations) {
        Assert.notNull(introspectionUri, "introspectionUri cannot be null");
        Assert.notNull(restOperations, "restOperations cannot be null");
        this.requestEntityConverter = this.defaultRequestEntityConverter(URI.create(introspectionUri));
        this.restOperations = restOperations;
    }

    private Converter<String, RequestEntity<?>> defaultRequestEntityConverter(URI introspectionUri) {
        return (token) -> {
            HttpHeaders headers = this.requestHeaders();
            MultiValueMap<String, String> body = this.requestBody(token);
            return new RequestEntity<>(body, headers, HttpMethod.POST, introspectionUri);
        };
    }

    private HttpHeaders requestHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        return headers;
    }

    private MultiValueMap<String, String> requestBody(String token) {
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("token", token);
        return body;
    }

    public OAuth2AuthenticatedPrincipal introspect(String token) {
        String userInfoKey = CacheConstants.USERINFOKEY + token;
        SecurityUser _user = (SecurityUser) redissonClient.getBucket(userInfoKey).get();
        if (ObjectUtils.isEmpty(_user)) {
            RequestEntity<?> requestEntity = this.requestEntityConverter.convert(token);
            if (requestEntity == null) {
                throw new OAuth2IntrospectionException("requestEntityConverter returned a null entity");
            } else {
                ResponseEntity<Map<String, Object>> responseEntity = this.makeRequest(requestEntity);
                Map<String, Object> claims = this.adaptToNimbusResponse(responseEntity);
                _user = (SecurityUser) this.convertClaimsSet(claims);
            }
        }
        redissonClient.getBucket(userInfoKey).set(_user);
        UserContextHolder.User user1 = new UserContextHolder.User();
        user1.setId(_user.getId());
        user1.setTenantId(_user.getTenantId());
        UserContextHolder.set(user1);
        return _user;
    }

    public void setRequestEntityConverter(Converter<String, RequestEntity<?>> requestEntityConverter) {
        Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
        this.requestEntityConverter = requestEntityConverter;
    }

    private ResponseEntity<Map<String, Object>> makeRequest(RequestEntity<?> requestEntity) {
        try {
            return this.restOperations.exchange(requestEntity, STRING_OBJECT_MAP);
        } catch (Exception var3) {
            throw new OAuth2IntrospectionException(var3.getMessage(), var3);
        }
    }

    private Map<String, Object> adaptToNimbusResponse(ResponseEntity<Map<String, Object>> responseEntity) {
        if (responseEntity.getStatusCode() != HttpStatus.OK) {
            throw new OAuth2IntrospectionException("Introspection endpoint responded with " + responseEntity.getStatusCode());
        } else {
            Map<String, Object> claims = responseEntity.getBody();
            if (claims == null) {
                return Collections.emptyMap();
            } else {
                boolean active = (Boolean)claims.compute("active", (k, v) -> {
                    if (v instanceof String) {
                        return Boolean.parseBoolean((String)v);
                    } else {
                        return v instanceof Boolean ? v : false;
                    }
                });
                if (!active) {
                    log.trace("Did not validate token since it is inactive");
                    throw new BadOpaqueTokenException("提供的token失效");
                } else {
                    return claims;
                }
            }
        }
    }

    private OAuth2AuthenticatedPrincipal convertClaimsSet(Map<String, Object> claims) {
        claims.computeIfPresent(IdTokenClaimNames.AUD, (k, v) -> v instanceof String ? Collections.singletonList(v) : v);
        claims.computeIfPresent("client_id", (k, v) -> v.toString());
        claims.computeIfPresent(IdTokenClaimNames.EXP, (k, v) -> Instant.ofEpochSecond(((Number)v).longValue()));
        claims.computeIfPresent(IdTokenClaimNames.IAT, (k, v) -> Instant.ofEpochSecond(((Number)v).longValue()));
        claims.computeIfPresent(IdTokenClaimNames.ISS, (k, v) -> v.toString());
        claims.computeIfPresent("nbf", (k, v) -> Instant.ofEpochSecond(((Number)v).longValue()));
        return JSON.parseObject(JSON.toJSONString(claims.get("user")),SecurityUser.class);
    }
    

//    @Override
//    public OAuth2AuthenticatedPrincipal introspect(String token) {
//        // 判断redis是否存在该token
//        // 存在token，直接返回用户信息
//        String userInfoKey = CacheConstants.USERINFOKEY + token;
////        User _user = (User) redissonClient.getBucket(userInfoKey).get();
//        SecurityUser _user = null;
//        if (!ObjectUtils.isEmpty(_user)) {
//            return _user;
//        }
//        // 不存在token，让认证授权服务器进行token验证
//        OAuth2AuthenticatedPrincipal principal = super.introspect(token);
//        // token剩余生效时间大于10秒放过
//        Instant now = Instant.now();
//        long expireTime = ChronoUnit.SECONDS.between(now, principal.getAttribute("exp"));
//        long minTime = 10L;
//        if (expireTime > minTime) {
//            Object object = principal.getAttribute(principal.getAttribute("sub"));
//            SecurityUser user = BeanUtil.toBean(object, SecurityUser.class);
//            // 放入缓存里
////            redissonClient.getBucket(userInfoKey).set(user, Duration.ofSeconds(expireTime));
//            UserContextHolder.User user1 = new UserContextHolder.User();
//            user1.setId(user.getId());
//            user1.setTenantId(user.getTenantId());
//            user1.setDeptId(user.getDeptId());
//            UserContextHolder.set(user1);
//            return user;
//        }
//        throw new OAuth2AuthenticationException("登录状态过期");
//    }

    private Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) {
        List<String> scopes = principal.getAttribute("authorities");
        if (ObjectUtils.isEmpty(scopes) || scopes.isEmpty()) {
            return new HashSet<>(0);
        }
        return scopes.stream()
                     .map(SimpleGrantedAuthority::new)
                     .collect(Collectors.toList());
    }
}